package com.ideaaedi.plugin.fastcoding.util;

import com.ideaaedi.plugin.fastcoding.settings.AppSettingsState;
import com.intellij.openapi.project.Project;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiArrayType;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiEnumConstant;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiModifierList;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiType;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.PsiUtil;
import org.apache.commons.lang3.StringUtils;

import java.math.BigDecimal;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * PsiClass相关操作工具类
 *
 * @author JustryDeng
 * @since 2021/12/18 10:18:04
 */
public final class JdPsiClassUtil {
    
    /** 找不到对应类时提示的格式 */
    private static final String CANNOT_DING_TYPE_TIPS_FORMAT = "[ERROR] Cannot found psi-class for type [%s].";
    
    /** 解析字段默认值失败 */
    private static final String CANNOT_PARSE_FIELD_DEFAULT_VALUE_FORMAT = "[ERROR] Parse default value for field-type [%s] error.";
    
    /** {@link JdPsiClassUtil#mockJson(PsiClass, Map, boolean, int)}递归深度限制 */
    private static final int DEEP_LIMIT = 10;
    
    /** 常用基础包装类及其默认值信息 */
    private static final Map<String, Supplier<?>> BASE_TYPE_VALUE_SUPPLIER_MAP = new HashMap<>(16);
    
    static {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        BASE_TYPE_VALUE_SUPPLIER_MAP.put("Boolean", (Supplier<Boolean>) () -> false);
        BASE_TYPE_VALUE_SUPPLIER_MAP.put("Byte", (Supplier<Byte>) () -> (byte)0);
        BASE_TYPE_VALUE_SUPPLIER_MAP.put("Short", (Supplier<Short>) () -> (short)0);
        BASE_TYPE_VALUE_SUPPLIER_MAP.put("Integer", (Supplier<Integer>) () -> 0);
        BASE_TYPE_VALUE_SUPPLIER_MAP.put("Long", (Supplier<Long>) () -> 0L);
        BASE_TYPE_VALUE_SUPPLIER_MAP.put("Float", (Supplier<Float>) () -> 0F);
        BASE_TYPE_VALUE_SUPPLIER_MAP.put("Double", (Supplier<Double>) () -> 0D);
        BASE_TYPE_VALUE_SUPPLIER_MAP.put("String", (Supplier<String>) () -> "");
        BASE_TYPE_VALUE_SUPPLIER_MAP.put("BigDecimal", (Supplier<BigDecimal>) () -> BigDecimal.ZERO);
        BASE_TYPE_VALUE_SUPPLIER_MAP.put("Date", (Supplier<String>) () -> dateTimeFormatter.format(LocalDateTime.now()));
        BASE_TYPE_VALUE_SUPPLIER_MAP.put("Timestamp", (Supplier<Timestamp>) () -> new Timestamp(System.currentTimeMillis()));
        BASE_TYPE_VALUE_SUPPLIER_MAP.put("LocalDate", (Supplier<LocalDate>) LocalDate::now);
        BASE_TYPE_VALUE_SUPPLIER_MAP.put("LocalTime", (Supplier<LocalTime>) LocalTime::now);
        BASE_TYPE_VALUE_SUPPLIER_MAP.put("LocalDateTime", (Supplier<LocalDateTime>) LocalDateTime::now);
    }
    
    private JdPsiClassUtil() {
        throw new UnsupportedOperationException("Un-support to create instance");
    }
    
    /**
     * 根据全类名找到相关的类
     *
     * @param project
     *         项目对象
     * @param classLongNameWithGenerics
     *         (可能带有泛型的)全类名  <br/>注：也是全类名
     * @return key - 全类名； v - 对应的PsiClass对象 <br/>注：v可能为null，以内k可能是 T U K V 这样之类的
     */
    public static Map<String, PsiClass> findAllClass(Project project, String classLongNameWithGenerics) {
        Set<String> classLongNameSet = Arrays.stream(classLongNameWithGenerics.split("[,<>]"))
                .filter(StringUtils::isNotBlank)
                .map(String::trim)
                .collect(Collectors.toSet());
    
        Map<String, PsiClass> result = new HashMap<>(8);
        for (String classLongName : classLongNameSet) {
            if (classLongName.contains(".")) {
                result.put(classLongName, JavaPsiFacade.getInstance(project).findClass(classLongName, GlobalSearchScope.allScope(project)));
            } else {
                // 可能是 T U E K V 这样的泛型
                result.put(classLongName, null);
            }
        }
        return result;
    }
    
    
    /**
     * 根据全类名找到相关的类
     *
     * @param project
     *         项目对象
     * @param classLongNameWithGenerics
     *         (可能带有泛型的)全类名  <br/>注：也是全类名
     * @return 第一个类 （可能为null）
     */
    public static PsiClass findClass(Project project, String classLongNameWithGenerics) {
        String firstClassLongName = Arrays.stream(classLongNameWithGenerics.split("[,<>]"))
                .filter(StringUtils::isNotBlank)
                .map(String::trim)
                .findFirst().orElseThrow(() -> new RuntimeException("Cannot findClass for " + classLongNameWithGenerics));
    
        if (firstClassLongName.contains(".")) {
            return JavaPsiFacade.getInstance(project).findClass(firstClassLongName, GlobalSearchScope.allScope(project));
        } else {
            // 可能是 T U E K V 这样的泛型
            return null;
        }
    }
    
    /**
     * mock psiType
     *
     * @param project
     *            项目
     * @param psiType
     *            要mock的类型
     * @return  mock结果
     */
    public static LinkedHashMap<String, Object> mockClass(Project project, PsiType psiType) {
        PsiClass psiClass;
        if (psiType instanceof PsiClassType) {
            psiClass = ((PsiClassType) psiType).resolve();
        } else {
            psiClass = JdPsiClassUtil.findClass(project, psiType.getCanonicalText());
        }
        if (psiClass != null) {
            return JdPsiClassUtil.mockJson(psiClass,
                    JdPsiTypeUtil.parseGenericNameAndSubstitutorTypeMapping(psiType),
                    AppSettingsState.getInstance(project).MockJson$containComment);
        }
        return new LinkedHashMap<>(1);
    }
    
    /**
     * mock指定类的字段数据
     *
     * @see JdPsiClassUtil#mockJson(PsiClass, Map, boolean, int)
     */
    public static LinkedHashMap<String, Object> mockJson(PsiClass psiClass, Map<String, PsiType> genericNameAndSubstitutorTypeMapping,
                                                         boolean containComment) {
        return mockJson(psiClass, genericNameAndSubstitutorTypeMapping, containComment, 1);
    }
    
    /**
     * mock指定类的字段数据
     *
     * @param psiClass
     *           目标class
     * @param genericNameAndSubstitutorTypeMapping
     *            psiClass类中涉及到的泛型以及实际使用时该泛型对应的实际类
     * @param containComment
     *            是否获取字段的注释
     * @param curDeep
     *            当前递归深度
     * @return mock出的指定类的字段数据
     */
    public static LinkedHashMap<String, Object> mockJson(PsiClass psiClass, Map<String, PsiType> genericNameAndSubstitutorTypeMapping,
                                                         boolean containComment, int curDeep) {
        LinkedHashMap<String, Object> mockedInfoMap = new LinkedHashMap<>(16);
        if (psiClass == null) {
            return mockedInfoMap;
        }
        if (curDeep > DEEP_LIMIT) {
            return mockedInfoMap;
        }
        if (skipMock(psiClass)) {
            // 不处理敏感包下的类
            return mockedInfoMap;
        }
        PsiField[] allFields = psiClass.getAllFields();
        for (PsiField field : allFields) {
            if (skipMock(field)) {
                continue;
            }
            PsiType type = field.getType();
                    String name = field.getName();
            // 添加注释
            if (containComment) {
                PsiDocComment docComment = field.getDocComment();
                if (docComment != null && docComment.getText() != null) {
                    mockedInfoMap.put(String.format("[注释] for [%s]", name), docComment.getText());
                }
            }
    
            String canonicalText = type.getCanonicalText();
            if (typeIsGeneric(type) && genericNameAndSubstitutorTypeMapping != null && genericNameAndSubstitutorTypeMapping.containsKey(canonicalText)) {
                mockedInfoMap.put(name, getTypeDefaultValue(type, genericNameAndSubstitutorTypeMapping.get(canonicalText), containComment));
            } else {
                mockedInfoMap.put(name, getTypeDefaultValue(type, null, containComment));
            }
        }
        return mockedInfoMap;
    }
    
    /**
     * 获取指定类型的默认值
     *
     * @param psiType
     *            类型
     * @param genericSubstitutorType
     *            当psiType是泛型时时，若genericSubstitutorType不为null,则采用genericSubstitutorType类型的默认值
     * @param containComment
     *            是否包含注释
     * @return  指定类型的默认值
     */
    @SuppressWarnings("MissingRecentApi")
    private static Object getTypeDefaultValue(PsiType psiType, PsiType genericSubstitutorType, boolean containComment) {
        // => primitive type
        if (psiType instanceof PsiPrimitiveType) {
            return PsiTypesUtil.getDefaultValue(psiType);
        }
        // => normal reference type
        String fieldTypeName = psiType.getPresentableText();
        if (isBaseType(fieldTypeName)) {
            return BASE_TYPE_VALUE_SUPPLIER_MAP.get(fieldTypeName).get();
        }
        // => other reference type
        // 数组
        if (psiType instanceof PsiArrayType) {
            final PsiType deepType = psiType.getDeepComponentType();
            List<Object> list = new ArrayList<>(1);
            final String deepTypeName = deepType.getPresentableText();
            if (deepType instanceof PsiPrimitiveType) {
                list.add(PsiTypesUtil.getDefaultValue(deepType));
            } else if (isBaseType(deepTypeName)) {
                list.add(BASE_TYPE_VALUE_SUPPLIER_MAP.get(deepTypeName).get());
            } else {
                PsiClass psiClass = PsiUtil.resolveClassInType(deepType);
                if (psiClass == null) {
                    list.add(String.format(CANNOT_DING_TYPE_TIPS_FORMAT, deepTypeName));
                } else {
                    list.add(mockJson(psiClass, JdPsiTypeUtil.parseGenericNameAndSubstitutorTypeMapping(deepType),
                            containComment, 1));
                }
            }
            return list;
        }
        // 集合 todo 继续完善
        if (fieldTypeName.startsWith("List")) {
            PsiType iterableType = PsiUtil.extractIterableTypeParameter(psiType, false);
            if (iterableType == null) {
                return String.format(CANNOT_PARSE_FIELD_DEFAULT_VALUE_FORMAT, fieldTypeName);
            }
            PsiClass iterableClass = PsiUtil.resolveClassInClassTypeOnly(iterableType);
            List<Object> list = new ArrayList<>(1);
            if (iterableClass == null) {
                list.add(String.format(CANNOT_DING_TYPE_TIPS_FORMAT, iterableType.getCanonicalText()));
            } else {
                String classTypeName = iterableClass.getName();
                if (isBaseType(classTypeName)) {
                    list.add(BASE_TYPE_VALUE_SUPPLIER_MAP.get(classTypeName).get());
                } else {
                    list.add(mockJson(iterableClass,
                            JdPsiTypeUtil.parseGenericNameAndSubstitutorTypeMapping(iterableType), containComment, 1));
                }
            }
            return list;
        }
        // 泛型
        if (typeIsGeneric(psiType)) {
            // 泛型替换者
            if (genericSubstitutorType != null) {
                return getTypeDefaultValue(genericSubstitutorType, null, containComment);
            }
            return new HashMap<>(1);
        }
        // 未知类型
        PsiClass psiClass = PsiUtil.resolveClassInType(psiType);
        if (psiClass == null) {
            return String.format(CANNOT_PARSE_FIELD_DEFAULT_VALUE_FORMAT, fieldTypeName);
        }
        // 枚举
        if (psiClass.isEnum()) {
            final PsiField[] fields = psiClass.getFields();
            List<Object> enumList = new ArrayList<>(16);
            for (PsiField field : fields) {
                if (field instanceof PsiEnumConstant) {
                    enumList.add(field.getName());
                }
            }
            return enumList;
        }
        // 其它类型
        return mockJson(psiClass, JdPsiTypeUtil.parseGenericNameAndSubstitutorTypeMapping(psiType), containComment, 1);
    }
    
    
    /**
     * psiType是否是泛型
     *
     * @param psiType
     *            类
     * @return  psiType是否是泛型
     */
    private static boolean typeIsGeneric(PsiType psiType) {
        Objects.requireNonNull(psiType, "psiType cannot be null.");
        // 非包装类
        if (psiType instanceof PsiPrimitiveType) {
            return false;
        }
        return !psiType.getCanonicalText().contains(".");
    }
    
    
    /**
     * 是否跳过指定的类
     *
     * @param psiClass
     *            类
     * @return  是否跳过指定的类
     */
    private static boolean skipMock(PsiClass psiClass) {
        final String qualifiedName = psiClass.getQualifiedName();
        return qualifiedName != null && (qualifiedName.startsWith("java") || qualifiedName.startsWith("sun."));
    }
    
    /**
     * 是否跳过指定的字段
     *
     * @param psiField
     *            字段
     * @return  是否跳过指定的字段
     */
    private static boolean skipMock(PsiField psiField) {
        final PsiModifierList psiModifierList = psiField.getModifierList();
        return psiModifierList != null && (
                psiModifierList.hasModifierProperty("static")
                        || psiModifierList.hasModifierProperty("transient")
        );
    }
    
    /**
     * 是否是常用基础类型
     *
     * @param typeName
     *            类型名称
     * @return  是否是常用基础类型
     */
    private static boolean isBaseType (String typeName) {
        return BASE_TYPE_VALUE_SUPPLIER_MAP.containsKey(typeName);
    }
    
}
